classdef MeasurementSession < handle
    %MEASUREMENTSESSION Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        measurement_name        
    end
    properties(Access = private)
        measurement_buffer
        measurement_data
        
        device
        repository
        start_time
    end

    %% Singleton Implementation
    methods (Static)
        function obj = getSession()
            persistent localObj;
            if isempty(localObj) || ~isvalid(localObj)
                localObj = MeasurementSession.MeasurementSession();
            end
            obj = localObj;
        end
    end

    methods (Access = private)
        function obj = MeasurementSession()
            %MEASUREMENTSESSION Construct an instance of this class
            %   Detailed explanation goes here
            L=log4m.getLogger;
            obj.measurement_name = 'NoName';
            obj.start_time = datetime('now');
            obj.update_repo_status();
            L.info('MeasurementSession::MeasurementSession', 'A new MeasurementSession object is created.');
        end
    end

    methods
        function delete(obj)
            fprintf('MeasurementSession::delete\t %s\n', ['The Session [started at ' datestr(obj.start_time) '] object is deleted from memory.']);
        end
        
        function disp(obj)
            fprintf('Session started at %s.\n\n', obj.start_time);
            fprintf('  [Core Rev.]: %s\n\n', obj.repository.core.revision(1:10));
            fprintf('  [App Rev.]: %s\n\n', obj.repository.app.revision(1:10));
            
            fprintf('  [Buffer]:');
            if ~isempty(obj.measurement_buffer)
                tags_buff = fields(obj.measurement_buffer);
                fprintf('\n');
                for k=1:length(tags_buff)
                    tag_k = tags_buff{k};
                    fprintf('    "%s" has %d data member(s). \n', tag_k, length(obj.measurement_buffer.(tag_k)) );
                end
                fprintf('\n');
            else
                fprintf(' Empty.\n\n');
            end
            
            fprintf('  [CommittedData]:');
            if ~isempty(obj.measurement_data)
                tags_data = fields(obj.measurement_data);
                fprintf('\n');
                for k=1:length(tags_data)
                    tag_k = tags_data{k};
                    fprintf('    "%s" has %d data member(s). \n', tag_k, length(obj.measurement_data.(tag_k)) );
                end
                fprintf('\n');
            else
                fprintf(' Empty.\n\n');
            end
            
            fprintf('  [Devices]:')
            if ~isempty(obj.device)
                fprintf('\n    %d device(s) in the device list.\n\n', length(obj.device.devices));
            else
                fprintf(' Empty.\n\n');
            end
            
        end
    end
    
    %% Device 
    methods
        function obj = connect_device(obj, xmlfile)            
            obj.disconnect_device();

            if nargin == 1
                xmlfile = obj.default_device_file();
            end
            
            p = Constant.AutoMeasurementDevicePath;
            d = Constant.PathDelimiter;            
            dp = MeasurementSession.DeviceFileParser([p d xmlfile]);
            
            obj.device = dp.connectDevices();
        end
        
        function obj = disconnect_device(obj)
            if isa(obj.device, 'MeasurementSession.DeviceFileParser')
                obj.device.disconnectDevices();
            end
            clear ziDAQ; pause(1);
            obj.device = [];
        end

        function dev = getDevice(obj, devname)
            dev = obj.device.getDevice(devname);
        end
        
        function showDevice(obj)
            disp(obj.device);
        end
        
        function zs = getZIServer(obj)
            zs = EquipmentControl.ZI.ziServer.ziServer(obj.findZIDeivcie);
        end
        
        function [ziDevNames, ziDev] = findZIDeivcie(obj)
            classNames = cellfun(@(x) x.class, obj.device.devices, 'UniformOutput', false);
            enableList = cellfun(@(x) x.enable, obj.device.devices);
            ziIndex = strcmp('EquipmentControl.ZI.ziMFLI', classNames);
            ziDev = obj.device.devices(ziIndex & enableList);
            ziDevNames = cellfun(@(x) x.name, ziDev, 'UniformOutput', false);
        end
    end
    
    %% Repository
    methods
        function obj = update_repo_status(obj)
            obj.repository.core = obj.get_repo_info('Core');
            obj.repository.app = obj.get_repo_info('App');
        end
        
        function showRepository(obj)
            fprintf('Repository info:\n');
            fprintf('[Core]:\n');
            disp(obj.repository.core);
            fprintf('[App]:\n');
            disp(obj.repository.app);
            fprintf('\n');
        end
    end
    
    %% Data
    methods
        function addData(obj, tag, data, description)
            L = log4m.getLogger;
            
            if nargin == 3
                description = 'none';
            end
            
            if ~isfield(obj.measurement_buffer, tag)
                obj.measurement_buffer.(tag) = [];
            end
            
            if isempty( obj.measurement_buffer.(tag) )
                obj.measurement_buffer.(tag) = MeasurementSession.SessionData(tag, data, description);
            else
                obj.measurement_buffer.(tag) = [obj.measurement_buffer.(tag), MeasurementSession.SessionData(tag, data, description)];
            end
            
            L.info('AbstractMeasurementSession::addData', sprintf('Data "%s" stored in buffer.', tag) );
        end
        
        function describe(obj, tag, text, varargin)
            L = log4m.getLogger;
            data = obj.measurement_buffer.(tag);
            
            p=inputParser;
            p.addParameter('index', 1:length(data), @isnumeric);
            p.parse(varargin{:});

            for k = 1:length(p.Results.index)
                old_desc = obj.measurement_buffer.(tag)(p.Results.index(k)).description;
                obj.measurement_buffer.(tag)(p.Results.index(k)).setDescription(text);
                L.info('AbstractMeasurementSession::describe', sprintf('Description of %s(%d): "%s" => "%s".', tag, k, old_desc, text) );
            end
        end
        
        function setMeasurementName(obj, name)
            L = log4m.getLogger;
            L.info('AbstractMeasurementSession::setMeasurementName', sprintf('Measurement name change: %s -> %s', obj.measurement_name, name));
            obj.measurement_name = name;
        end
        
        function mark(obj, tag, varargin)
            L = log4m.getLogger;
            data = obj.measurement_buffer.(tag);
            
            p=inputParser;
            p.addParameter('index', 1:length(data), @isnumeric);
            p.parse(varargin{:});

            for k = 1:length(p.Results.index)
                obj.measurement_buffer.(tag)(p.Results.index(k)).setStatus(true);
                L.info('AbstractMeasurementSession::mark', sprintf('Data %s(%d) marked.', tag, p.Results.index(k)) );
            end
        end
        
        function unMark(obj, tag, varargin)
            L = log4m.getLogger;
            data = obj.measurement_buffer.(tag);
            
            p=inputParser;
            p.addParameter('index', 1:length(data), @isnumeric);
            p.parse(varargin{:});

            for k = 1:length(p.Results.index)
                obj.measurement_buffer.(tag)(p.Results.index(k)).setStatus(false);
                L.info('AbstractMeasurementSession::mark', sprintf('Data %s(%d) unmarked.', tag, p.Results.index(k)) );
            end
        end
        
        function showBuffer(obj, tag)
            if ~isempty(obj.measurement_buffer)
                if nargin == 1
                    tag =  fields(obj.measurement_buffer);
                elseif nargin == 2
                    if ischar(tag)
                        tag = {tag};
                    end
                end

                for k = 1:length(tag)
                    fprintf('\n');
                    fprintf('-- Buffered Data with Tag "%s"\n\n', tag{k});
                    disp(obj.measurement_buffer.(tag{k}));
                    fprintf('\n\n');
                end
            else
                fprintf('No data in buffer.\n');
            end
        end
        
        function showCommitData(obj, tag)
            if ~isempty(obj.measurement_data)
                if nargin == 1
                    tag =  fields(obj.measurement_data);
                elseif nargin == 2
                    if ischar(tag)
                        tag = {tag};
                    end
                end
                
                for k = 1:length(tag)
                    fprintf('\n');
                    fprintf('-- Committed Data with Tag "%s"\n\n', tag{k});
                    disp(obj.measurement_data.(tag{k}));
                    fprintf('\n\n');
                end
            else
                fprintf('No data found.\n');
            end
        end
        
        function commitData(obj)
            L = log4m.getLogger;
            obj.measurement_data = struct();
            tags = fields(obj.measurement_buffer);
            for k = 1:length(tags)
                tag = tags{k};
                data = obj.measurement_buffer.(tag);
                marked = logical([data.status]);
                if any(marked)
                    obj.measurement_data.(tag) = data(marked);
                    L.info('AbstractMeasurementSession::commitData', sprintf('Data "%s" stored.', tag) );
                end
            end
        end
        
        function data = getBufferData(obj, tag, k)
            if nargin == 3
                data_all = obj.measurement_buffer.(tag);
                data = data_all(k);
            elseif nargin == 2
                data_all = obj.measurement_buffer.(tag);
                data = data_all;
            elseif nargin == 1
                data = obj.measurement_buffer;
            end
        end
        function data = getCommitData(obj, tag, k)
            data_all = obj.measurement_data.(tag);
            if nargin == 3
                data = data_all(k);
            elseif nargin == 2
                data = data_all;
            end
        end
        function tags = getBufferTags(obj)
            if ~isempty(obj.measurement_buffer)
                tags = fields(obj.measurement_buffer);
            else
                tags = [];
            end
        end
        
        function showGUI(obj)
            f = uifigure('Name', 'Session Buffer', 'Position', [100, 100, 1200, 800]);
            uit = uitable(f, 'Position', [50 100 800 600], 'ColumnWidth',{'auto', 50 'auto', 50, 200, 'auto', 100});
            uib = uibutton(f, 'Position', [700 30 100 25], 'Text', 'Save');
            uitext = uitextarea(f, 'Position', [50, 30, 500, 25]);
            
            uitext.Value = {obj.measurement_name};
            
            uit.ColumnName = {'Tag', 'Index', 'Class', 'Bytes', 'Time','Description','Marked'};
            uit.ColumnEditable = [false, false, false, false, false, true, true];
            
            uit.CellEditCallback = @dataChange;
            uit.CellSelectionCallback = @dataSelect;
            uib.ButtonPushedFcn = @saveData;
            uitext.ValueChangedFcn = @reName;
            
            tag = obj.getBufferTags;
            
            tableData = {};
            for k = 1:length(tag)
                dTag=obj.getBufferData(tag{k});
                for q = 1:length(dTag)
                    d = obj.getBufferData(tag{k}, q); info = whos('d');
                    data_q = {tag{k}, q, info.class, info.bytes, datestr(d.timestamp), d.description, d.status};
                    tableData = [tableData; data_q]; %#ok<AGROW>
                end
            end
            uit.Data = tableData;
        end
        
    end
    
    %% Externally defined functions
    methods
        filename = default_device_file(~)
        info = get_repo_info(~, repo)
        [filename_out, variable_name] = save( session, name ) 
    end

end

function dataChange(src,eventdata)
    getSession;
    tag = src.Data{eventdata.Indices(1), 1};
    q = src.Data{eventdata.Indices(1), 2};
    
    switch eventdata.Indices(2) 
        case 6
            sess.describe(tag, eventdata.NewData, 'index', q);
        case 7
            if ~eventdata.PreviousData 
                sess.mark(tag, 'index', q);
            else
                sess.unMark(tag, 'index', q);
            end            
    end
end

function dataSelect(src,eventdata)
    getSession;
    tag = src.Data{eventdata.Indices(1), 1};
    q = src.Data{eventdata.Indices(1), 2};
    meas_data = sess.getBufferData(tag, q).data;
    
    if any(contains(methods(meas_data), 'plot_data_group'))
        src.Parent.Children(2).Enable = 'on';
        src.Parent.Children(2).UserData = {tag, q};
    else
        src.Parent.Children(2).Enable = 'on';
        src.Parent.Children(2).UserData = {tag, q};
    end
end

function saveData(~, ~)
    getSession;
    sess.commitData();
    sess.save(sess.measurement_name);
end

function reName(src, ~)
    getSession;
    sess.setMeasurementName(src.Value{1});
end
